/*
Copyright (c) 2010 julien barbay <barbay.julien@gmail.com>

 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
 files (the "Software"), to deal in the Software without
 restriction, including without limitation the rights to use,
 copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following
 conditions:

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
*/

//TODO : Looks like it's working great
package martian.m4gic.services
{
	import flash.display.*;
	import flash.events.*;
	import flash.external.ExternalInterface;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;
	import flash.system.Capabilities;
	import flash.utils.Timer;
	
	public class DeepLinking
	{
		public function DeepLinking() { trace("6:", NAME, "class must not be instanciated.") }
		
		/* service name */
		static private const NAME:String = "DeepLinking";
		
		/* service properties */
		static public const INTERNAL:String = "internal";
		static public const EXTERNAL:String = "external";
		
		static private var _value:String;
		static private var _strict:Boolean;
		static private var queue:Array;
		static private var js:Boolean;
		static private var init:Boolean = false;
		static private var external:Boolean = true;
		static private var locked:Boolean = false;
		
		/* service initialization */
		static private function _register(args:Object = null):void
		{
			_value = "";
			_strict = true;
			queue = new Array();
			js = ExternalInterface.available;
		}
		
		static private function _release(args:Object = null):void {}
		static private function _activate(args:Object = null):void
		{
			if (js)
			{
				try
				{
					js = ExternalInterface.call('function() { return (typeof SWFAddress != "undefined"); }') as Boolean;
					
					ExternalInterface.addCallback('getSWFAddressValue', function():String { return _value });
					ExternalInterface.addCallback('setSWFAddressValue', set_value);
					
				} catch (e:Error) { js = false; }
			}           
			
			var timer:Timer = new Timer(10);
			timer.addEventListener(TimerEvent.TIMER, check, false, 0, true);
			timer.start();
		}
		static private function _desactivate(args:Object = null):void {}
		
		/* service methods */
		static private function call(fn:String, param:Object=''):void
		{
			if (js && available)
			{
				if (Capabilities.os.indexOf('Mac') != -1)
				{
					if (queue.length == 0)
					{
						var timer:Timer = new Timer(10);
						timer.addEventListener(TimerEvent.TIMER, stack);
						timer.start();
					}
					
					queue.push({ fn:fn, param:param });
				} else { ExternalInterface.call(fn, param); }
			}
		}

		static private function check(e:TimerEvent):void
		{
			if (hasEventListener(Event.INIT) && !init)
			{
				set_value(get_value(), false);
				init = true;
			}
			
			if (hasEventListener(Event.CHANGE))
			{
				e.target.stop();
				init = true;
				set_value(get_value(), false);
			}
		}
		
		static private function stack(e:TimerEvent):void
		{
			if (queue.length > 0)
			{
				var script:String = '';
				
				for each(var obj:Object in queue)
				{
					if (obj.param is String) { obj.param = '"' + obj.param + '"'; }
					script += obj.fn + '(' + obj.param + ');';
				}
				
				queue = new Array();
				navigateToURL(new URLRequest('javascript:' + script + 'void(0);'), '_self');
			}
			else { e.target.stop(); }
		}
		
		static public function get strict():Boolean
		{
			if (available)
			{
				var str:String = "";
				if (js) { str = ExternalInterface.call('SWFAddress.getStrict') as String; }
				
				return (str != "") ? (str == 'true') : _strict;
			}
			
			return _strict;
		}
		static public function set strict(bool:Boolean):void { if (available) { call('SWFAddress.setStrict', bool); } }
		
		
		static private function isStrict(str:String, force:Boolean = false):String
		{
			if (strict)
			{
				if (force) { if (str.substr(0, 1) != '/') { str = '/' + str; } }
				else { if (str == '') { str = '/'; } }
			}
			
			return str;
		}

		//JS INTERFACE FUNCTIONS
		static private function get_value():String
		{
			var str:String, ids:String = null;
			
			if (js)
			{ 
				str = ExternalInterface.call('SWFAddress.getValue') as String;
				
				var arr:Array = ExternalInterface.call('SWFAddress.getIds') as Array;
				if (arr != null) { ids = arr.toString(); }
			}
			
			if (ids == null || !js) { str = _value; }
			else if (str == 'undefined' || value == null) { str = ''; }
			
			return isStrict(str || '');
		}
		
		static private function set_value(str:String, verify:Boolean = true):void
		{
			if (available)
			{
				if (locked) { return; }
				
				if (verify)
				{
					if (value == 'undefined' || value == null) { value = ''; }
					if (str == value && init) { return; }
				}
				
				_value = str;
				
				if (!init)
				{
					init = true;
					dispatchEvent(new Event(Event.INIT));
				}
				else
				{
					
					dispatchEvent(new Event(external ? EXTERNAL : INTERNAL));
					dispatchEvent(new Event(Event.CHANGE));
					
					external = true;
				}
			}
		}        
		
		//OVERFLOW SECURITY
		static public function lock():void { locked = true; }
		static public function unlock():void { locked = false; }
		
		//BROWSER VARS
		static public function get url():String
		{
			if (available)
			{		
				var str:String = null;
				
				if (js) { str = String(ExternalInterface.call('SWFAddress.getBaseURL')); }
				return (str == null || str == 'null' || !js) ? '' : str;
			}
			
			return "";
		}
				
		static public function get value():String { return available ? decodeURI(isStrict(_value || '')) : ""; }
		static public function set value(str:String):void
		{
			if (available)
			{
				if (locked) { return; }
				
				if (str == 'undefined' || str == null) { str = ''; }
				
				str = encodeURI(decodeURI(isStrict(str, true)));
				
				if (str == _value) { return; }
				
				_value = str;
				
				call('console.log', str);
				call('SWFAddress.setValue', str);
				
				if (init) { external = false; }
			}
		}		
		
		static public function get title():String
		{
			if (available)
			{
				var str:String = (js) ? ExternalInterface.call('SWFAddress.getTitle') as String : '';
				if (str == 'undefined' || str == null) { str = ''; }
				
				return decodeURI(str);
			}
			
			return "";
		}
		
		static public function set title(str:String):void { if (available) { call('SWFAddress.setTitle', encodeURI(decodeURI(str))); } }
		
		static public function get status():String
		{
			if (available)
			{
				var str:String = (js) ? ExternalInterface.call('SWFAddress.getStatus') as String : '';
				if (str == 'undefined' || str == null) { str = ''; }
				
				return decodeURI(str);
			}
			
			return "";
		}
		
		static public function set status(str:String):void
		{
			if (available)
			{
				if (str == "") { call('SWFAddress.resetStatus'); }
				else { call('SWFAddress.setStatus', encodeURI(decodeURI(str))); }
			}
		}
		
		static public function get path():Array
		{
			if (available) 
			{
				var str:String = value.replace(new RegExp("\/{2,}", "g"), "/");
				if (str.indexOf('?') != -1) { str = str.split('?')[0]; }
				if (str.charAt(str.length - 1) != "/") { str = str + "/"; }
				
				var p:Array = str.split('/');
					p.splice(0, 1);
					p.splice(p.length - 1, 1);
				
				return p;
			}
			
			return null;
		}
		
		static public function get parameters():Object
		{
			var params:Object = null, str:String = value, index:int = str.indexOf('?');
			
			if (available && index > -1)
			{
				params = new Object();
				
				str = str.substr(index + 1);
				
				var source:Array = str.split('&'), tmp:Array;
				var n:String, v:String;
				
				for each (var property:String in source)
				{
					tmp = property.split("=");
					params[tmp[0]] = tmp[1];
				}
			}
			
			return params;
		}
		
		static public function get history():Boolean { return (available && js) ? ExternalInterface.call('SWFAddress.getHistory') as Boolean : false; }
		static public function set history(history:Boolean):void { if (available) { call('SWFAddress.setHistory', history); } }

		//BROWSER COMMANDS
		static public function back():void { if (available) { call('SWFAddress.back'); } }
		static public function forward():void { if (available) { call('SWFAddress.forward'); } }
		
		static public function up():void
		{
			if (available)
			{
				var path:Array = path;
				path.splice(path.length - 1, 1);
				
				value = path.join("/");
			}
		}
		
		static public function go(delta:int):void { if (available) { call('SWFAddress.go', delta); } }
		
		static public function href(url:String, target:String = '_self'):void
		{
			if (available)
			{
				if (js && Capabilities.playerType == 'ActiveX') { ExternalInterface.call('SWFAddress.href', url, target); }
				else { navigateToURL(new URLRequest(url), target); }
			}
		}
		
		static public function popup(url:String, name:String = 'popup', options:String = '""', handler:String = ''):void
		{
			if (available)
			{
				if (js && (Capabilities.playerType == 'ActiveX' || ExternalInterface.call('asual.util.Browser.isSafari'))) { ExternalInterface.call('SWFAddress.popup', url, name, options, handler); }
				else { navigateToURL(new URLRequest('javascript:popup=window.open("' + url + '","' + name + '",' + options + ');' + handler + ';void(0);'), '_self'); }
			}
		}
		
		
		
		
		
		/* inheritance */
		static private var reference:Sprite;
		static private var dispatch:EventDispatcher;
		static private var stage:Stage;
		
		static private var registered:Boolean = false;
		static private var activated:Boolean = false;
			static public function get available():Boolean { return (registered && activated); }
		
		static public function register(container:Sprite, args:Object = null):void
		{
			if (!registered)
			{
				reference = container;
				dispatch = new EventDispatcher();
				stage = reference.stage;
				
				_register(args);
				
				registered = true;
				
				if (args != null) { if (args.active != undefined) { if (args.active == true) { activate(); } } }
			}
			else { trace("6:", NAME, "already registered."); }
		}
		
		static public function release(args:Object = null):void
		{
			if (registered && !activated)
			{
				stage = null;
				dispatch = null;
				reference = null;
				
				_release(args);
				
				registered = false;
			}
			else { trace("6:", NAME, "already released."); }
		}
		
		static public function activate(args:Object = null):void
		{
			if (registered && !activated)
			{
				activated = true;
				_activate(args);
			}			
		}
		
		static public function desactivate(args:Object = null):void
		{
			if (registered && activated)
			{
				activated = false;
				_desactivate(args);
			}
		}
		
		static private function dispatchEvent(event:Event):void
		{
			dispatch.dispatchEvent(event);
		}
		
		static public function hasEventListener(type:String):Boolean
		{
			return dispatch.hasEventListener(type);
		}
		
		static public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
		{
			dispatch.addEventListener(type, listener, useCapture, priority, useWeakReference);
		}
		
		static public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
		{
			dispatch.removeEventListener(type, listener, useCapture);
		}
	}
}